#include "comono.h"
#include "XMLHttpRequestClass.h"
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFSIZE 5128

/* 
 * XMLHttpRequest and DOM Exception codes 
 * http://www.w3.org/TR/XMLHttpRequest/#exceptions
 * http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-17189187
 */

const unsigned short SUCCESS = 0;
const unsigned short INDEX_SIZE_ERR = 1;
const unsigned short DOMSTRING_SIZE_ERR = 2;
const unsigned short HIERARCHY_REQUEST_ERR = 3;
const unsigned short WRONG_DOCUMENT_ERR = 4;
const unsigned short INVALID_CHARACTER_ERR = 5;
const unsigned short NO_DATA_ALLOWED_ERR = 6;
const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7;
const unsigned short NOT_FOUND_ERR = 8;
const unsigned short NOT_SUPPORTED_ERR = 9;
const unsigned short INUSE_ATTRIBUTE_ERR = 10;
const unsigned short INVALID_STATE_ERR = 11;
const unsigned short SYNTAX_ERR = 12;
const unsigned short INVALID_MODIFICATION_ERR = 13;
const unsigned short NAMESPACE_ERR = 14;
const unsigned short INVALID_ACCESS_ERR = 15;
const unsigned short VALIDATION_ERR = 16;
const unsigned short TYPE_MISMATCH_ERR = 17;
const unsigned short SECURITY_ERR = 18;
const unsigned short NETWORK_ERR = 101;
const unsigned short ABORT_ERR = 102;

JSBool XMLHttpRequestClass_AddProperty(JSContext *cx, JSObject *obj,
                                       jsval id, jsval *vp);
JSBool XMLHttpRequestClass_GetProperty(JSContext *cx, JSObject *obj,
                                       jsval id, jsval *vp);
JSBool XMLHttpRequestClass_SetProperty(JSContext *cx, JSObject *obj,
                                       jsval id, jsval *vp);
JSBool XMLHttpRequestClass_Resolve(JSContext *cx, JSObject *obj,
                                   jsval id);

void XMLHttpRequestClass_Finalize(JSContext *cx, JSObject *obj);

static JSClass XMLHttpRequestClass = {
    "XMLHttpRequest", JSCLASS_HAS_PRIVATE,
    XMLHttpRequestClass_AddProperty, JS_PropertyStub,
    XMLHttpRequestClass_GetProperty, XMLHttpRequestClass_SetProperty,
    JS_EnumerateStub, JS_ResolveStub,
    JS_ConvertStub, XMLHttpRequestClass_Finalize,
    JSCLASS_NO_OPTIONAL_MEMBERS
};

JSBool
XMLHttpRequestClass_AddProperty(JSContext *cx, JSObject *obj,
				jsval id, jsval *vp)
{
    return JS_TRUE;
}

JSBool
XMLHttpRequestClass_GetProperty(JSContext *cx, JSObject *obj,
                                jsval id, jsval *vp)
{
    gchar *key = JS_GetStringBytes(JS_ValueToString(cx, id));
    if (g_strcasecmp(key, "responseStream") == 0) {
        xmlHttpRequest *xhr = JS_GetPrivate(cx, obj);
        if (xhr->readyState == 4) {
            GString *cache_file_path = g_string_new(g_get_home_dir());
            JSString *str = NULL;
            g_string_append(cache_file_path, "/");
            g_string_append(cache_file_path, DESKTOP_GADGET_PLAYER_SUBDIR);
            g_string_append(cache_file_path, CACHE_SUBDIR);

            g_string_append_printf(cache_file_path, "%u",
                                   g_str_hash(xhr->url));
            g_debug("responseStream url %s, path %s", xhr->url,
                    cache_file_path->str);
            str = JS_NewStringCopyZ(cx, cache_file_path->str);
            *vp = STRING_TO_JSVAL(str);
            g_string_free(cache_file_path, TRUE);
        } else {
            *vp = JSVAL_VOID;
        }
    }
    return JS_TRUE;
}

JSBool
XMLHttpRequestClass_SetProperty(JSContext *cx, JSObject *obj, jsval id,
                                jsval *vp)
{
    return JS_TRUE;
}

JSBool
XMLHttpRequestClass_Resolve(JSContext *cx, JSObject *obj, jsval id)
{
    return JS_TRUE;
}

void XMLHttpRequestClass_Finalize(JSContext *cx, JSObject *obj)
{
    xmlHttpRequest *xhr = JS_GetPrivate(cx, obj);
    xmlHttpRequest_free(xhr);
}

static JSBool
XMLHttpRequestClass_new(JSContext *cx, JSObject *obj, uintN argc,
                        jsval *argv, jsval *rval);
static JSBool XMLHttpRequestMethod_send(JSContext *cx, JSObject *obj,
                                        uintN argc, jsval *argv,
                                        jsval *rval);
static JSBool XMLHttpRequestMethod_open(JSContext *cx, JSObject *obj,
                                        uintN argc, jsval *argv,
                                        jsval *rval);
static JSBool
XMLHttpRequestMethod_abort(JSContext *cx, JSObject *obj,
			   uintN argc, jsval *argv, jsval *rval);
static JSBool
XMLHttpRequestMethod_setRequestHeader(JSContext *cx, JSObject *obj,
				      uintN argc, jsval *argv, jsval *rval);
static JSBool
XMLHttpRequestMethod_getAllResponseHeaders(JSContext *cx, JSObject *obj,
					   uintN argc, jsval *argv, jsval *rval);
static JSBool
XMLHttpRequestMethod_getResponseHeader(JSContext *cx, JSObject *obj,
				       uintN argc, jsval *argv, jsval *rval);

gboolean
xmlHttpRequest_changeReadyState(xmlHttpRequest *xhr, gushort state);
unsigned short get_socket_handle(xmlHttpRequest *xhr, gchar *hostname,
				 gchar *port, gint *fd);
void send_implement(xmlHttpRequest *xhr);
gchar *readline(GString *message, int *i);
unsigned short parseMessage(xmlHttpRequest *xhr, GString *message);
unsigned short parseHeaderLine(xmlHttpRequest *xhr, const gchar *line);
gchar *request_headers_new(GHashTable *headers);

JSFunctionSpec XMLHttpRequest_methods[] = {
    {"open", XMLHttpRequestMethod_open, 2, 0, 0},
    {"setRequestHeader", XMLHttpRequestMethod_setRequestHeader, 2, 0, 0},
    {"send", XMLHttpRequestMethod_send, 0, 0, 0},
    {"abort", XMLHttpRequestMethod_abort, 0, 0, 0},
    {"getAllResponseHeaders", XMLHttpRequestMethod_getAllResponseHeaders,
     0, 0, 0},
    {"getResponseHeader", XMLHttpRequestMethod_getResponseHeader, 1, 0, 0},
    {NULL, NULL, 0, 0, 0}
};

static JSBool
XMLHttpRequestClass_new(JSContext *cx, JSObject *obj,
                        uintN argc, jsval *argv, jsval *rval)
{
    xmlHttpRequest *xhr = xmlHttpRequest_new(cx, obj);
    JS_SetPrivate(cx, obj, xhr);
    xmlHttpRequest_changeReadyState(xhr, UNSENT);
    return JS_TRUE;
}

static JSBool
XMLHttpRequestMethod_send(JSContext *cx, JSObject *obj,
                          uintN argc, jsval *argv, jsval *rval)
{
    xmlHttpRequest *xhr = (xmlHttpRequest *) JS_GetPrivate(cx, obj);
    gchar *query = NULL;
    unsigned short result;
    g_debug("XMLHttpRequest send Method called.");
    g_return_val_if_fail(xhr, JS_FALSE);

    if (argc == 1){
        query = g_strdup(JS_GetStringBytes(JS_ValueToString(cx, argv[0])));
    }
    result = xmlHttpRequest_send(xhr, query);
    if (result != SUCCESS) {
        jsval result_v = INT_TO_JSVAL(result);
        g_debug("throwing exception code %d", result);
        JS_SetPendingException(cx, result_v);
        return JS_FALSE;
    }
    return JS_TRUE;
}

static JSBool
XMLHttpRequestMethod_open(JSContext *cx, JSObject *obj,
                          uintN argc, jsval *argv, jsval *rval)
{
    xmlHttpRequest *xhr = NULL;
    gchar *method = NULL, *url = NULL, *user = NULL, *password = NULL;
    gboolean async = FALSE;
    jsval readyState;
    xhr = (xmlHttpRequest *) JS_GetPrivate(cx, obj);
    if (!xhr) {
        g_warning("Private pointer is not set.");
        return JS_FALSE;
    }

    switch (argc) {
    case 5: 
	password = g_strdup(JS_GetStringBytes(JS_ValueToString(cx, argv[4])));
    case 4: 
	user = g_strdup(JS_GetStringBytes(JS_ValueToString(cx, argv[3])));
    case 3: {
	if (JSVAL_IS_BOOLEAN(argv[2]))
	    async = (JSVAL_TO_BOOLEAN(argv[2]) == JS_TRUE) ? TRUE : FALSE;
	else
	    break;
    }
    default: {
	gchar ch;
	gchar *original;
	GString *escaped = g_string_new(NULL);
	method = g_strdup(JS_GetStringBytes(JS_ValueToString(cx, argv[0])));
	if (strlen(method) == 0)
	    break;

	original = JS_GetStringBytes(JS_ValueToString(cx, argv[1]));
	while (*original != '\0') {
	    ch = *original++;
	    if (ch == ' ')
		g_string_append(escaped, "%20");
	    else
		g_string_append_c(escaped, ch);
	}
	url = g_string_free(escaped, FALSE);
	printf("method : %s url : %s\n", method, url);
	xmlHttpRequest_open(xhr, method, url, async, user, password);
    }
    }
    readyState = INT_TO_JSVAL(xhr->readyState);
    JS_SetProperty(cx, obj, "readyState", &readyState);
    g_free(method);
    g_free(url);
    g_free(user);
    g_free(password);
    return JS_TRUE;
}

static JSBool
XMLHttpRequestMethod_abort(JSContext *cx, JSObject *obj,
                           uintN argc, jsval *argv, jsval *rval)
{
    xmlHttpRequest *xhr = JS_GetPrivate(cx, obj);
    g_return_val_if_fail(xhr, JS_TRUE);
    xmlHttpRequest_abort(xhr);
    return JS_TRUE;
}

static JSBool
XMLHttpRequestMethod_setRequestHeader(JSContext *cx, JSObject *obj,
                                      uintN argc, jsval *argv, jsval *rval)
{
    xmlHttpRequest *xhr = NULL;
    gchar *header = NULL, *value = NULL;
    xhr = (xmlHttpRequest *) JS_GetPrivate(cx, obj);
    g_return_val_if_fail(xhr, JS_FALSE);
    g_return_val_if_fail(argc == 2, JS_FALSE);
    header =
        g_strdup((gchar *) JS_GetStringBytes(JS_ValueToString(cx, argv[0])));
    value =
        g_strdup((gchar *) JS_GetStringBytes(JS_ValueToString(cx, argv[1])));
    xmlHttpRequest_setRequestHeader(xhr, header, value);

    g_free(header);
    g_free(value);
    return JS_TRUE;
}

static JSBool
XMLHttpRequestMethod_getAllResponseHeaders(JSContext *cx, JSObject *obj,
                                           uintN argc, jsval *argv, jsval *rval)
{
    gchar *allHeaders;
    xmlHttpRequest *xhr = JS_GetPrivate(cx, obj);
    g_return_val_if_fail(xhr, JS_FALSE);

    allHeaders = xmlHttpRequest_getAllResponseHeaders(xhr);
    *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, allHeaders));
    g_free(allHeaders);
    return JS_TRUE;
}

static JSBool
XMLHttpRequestMethod_getResponseHeader(JSContext *cx, JSObject *obj,
                                       uintN argc, jsval *argv, jsval *rval)
{
    xmlHttpRequest *xhr = NULL;
    gchar *header = NULL, *response = NULL;
    JSString *response_jsstr;

    xhr = (xmlHttpRequest *) JS_GetPrivate(cx, obj);
    g_return_val_if_fail(xhr, JS_FALSE);
    g_return_val_if_fail(argc == 1, JS_FALSE);

    header = JS_GetStringBytes(JS_ValueToString(cx, argv[0]));
    g_return_val_if_fail(header, JS_FALSE);
    response = xmlHttpRequest_getResponseHeader(xhr, header);
    if(response){
	response_jsstr = JS_NewStringCopyZ(cx, response);
	g_free(response);
	*rval = STRING_TO_JSVAL(response_jsstr);
    }else{
	*rval = JSVAL_VOID;
    }
    return JS_TRUE;
}

gboolean
xmlHttpRequest_changeReadyState(xmlHttpRequest *xhr, gushort state)
{
    jsval readyState, handler, rval, argv;
    JSObject *view;
    xhr->readyState = state;
    readyState = INT_TO_JSVAL(xhr->readyState);
    JS_SetProperty(xhr->cx, xhr->obj, "readyState", &readyState);

    JS_GetProperty(xhr->cx, xhr->obj, "onreadystatechange", &handler);
    view = JS_GetContextPrivate(xhr->cx);
    if (JSVAL_IS_OBJECT(handler)
        && JS_ObjectIsFunction(xhr->cx, JSVAL_TO_OBJECT(handler))) {
        JSBool result =
            JS_CallFunctionValue(xhr->cx, view, handler, 0, &argv,
                                 &rval);
        return result;
    } else {
        if (JSVAL_IS_VOID(handler)) {
            return JS_TRUE;
        }
        return JS_FALSE;
    }
    return TRUE;
}

gboolean XMLHttpRequestClass_define_constructor(js_engine *engine)
{
    JSObject *proto;
    g_return_val_if_fail(engine, FALSE);
    proto = JS_InitClass(engine->cx, engine->glob, NULL,
                         &XMLHttpRequestClass, XMLHttpRequestClass_new, 0,
                         NULL, XMLHttpRequest_methods, NULL, NULL);
    g_return_val_if_fail(proto, FALSE);
    return TRUE;
}

xmlHttpRequest *xmlHttpRequest_new(JSContext *cx, JSObject *obj)
{
    xmlHttpRequest *new = (xmlHttpRequest *) g_new0(xmlHttpRequest, 1);
    new->requestHeaders = g_hash_table_new_full(g_str_hash, g_str_equal,
                                                g_free, g_free);
    new->responseHeaders = g_hash_table_new_full(g_str_hash, g_str_equal,
                                                 g_free, g_free);
    new->readyState = UNSENT;
    new->responseHeader = g_string_new(NULL);
    new->responseText = g_string_new(NULL);

    new->cx = cx;
    new->obj = obj;
    return new;
}

gboolean xmlHttpRequest_abort(xmlHttpRequest *xhr)
{
    g_thread_join(xhr->thread);
    return TRUE;
}

void xmlHttpRequest_free(xmlHttpRequest *xhr)
{
    if(xhr){
	g_free(xhr->method);
	g_free(xhr->url);
	g_free(xhr->user);
	g_free(xhr->password);
	g_free(xhr->query);

	g_hash_table_destroy(xhr->requestHeaders);
	g_hash_table_destroy(xhr->responseHeaders);

	g_string_free(xhr->responseHeader, TRUE);
	g_string_free(xhr->responseText, TRUE);

	g_free(xhr);
    }
}

gboolean
xmlHttpRequest_open(xmlHttpRequest *xhr, const gchar *method,
                    const gchar *url, gboolean async,
                    const gchar *user, const gchar *password)
{
    g_debug("open: %s %s, %d, %s, %s", method, url, async, user, password);
    xhr->readyState = OPENED;
    xhr->method = g_strdup(method);
    xhr->async = async;
    xhr->url = g_strdup(url);
    xhr->user = g_strdup(user);
    xhr->password = g_strdup(password);
    return TRUE;
}

void
xmlHttpRequest_setRequestHeader(xmlHttpRequest *xhr,
                                const gchar *header, const gchar *value)
{
    g_hash_table_insert(xhr->requestHeaders, g_strdup(header), g_strdup(value));
}

void
xmlHttpRequest_setResponseHeader(xmlHttpRequest *xhr,
                                 const gchar *header, const gchar *value)
{
    g_hash_table_insert(xhr->responseHeaders, g_strdup(header),
                        g_strdup(value));
}

gchar *xmlHttpRequest_getAllResponseHeaders(xmlHttpRequest *xhr)
{
    GList *headers = g_hash_table_get_keys(xhr->responseHeaders);
    GString *str = g_string_new(NULL);
    gchar *key = NULL, *value = NULL;
    int i;
    for (i = 0; i < g_list_length(headers); i++) {
        key = g_list_nth_data(headers, i);
        value = g_hash_table_lookup(xhr->responseHeaders, key);
        g_string_append(str, key);
        g_string_append(str, ": ");
        g_string_append(str, value);
        g_string_append(str, "\r\n");
    }
    return g_string_free(str, FALSE);
}

gchar *xmlHttpRequest_getResponseHeader(xmlHttpRequest *xhr, const gchar *header)
{
    gchar *value = g_hash_table_lookup(xhr->responseHeaders, header);
    if(value)
	return g_strdup(value);
    else
	return NULL;
}

gchar *xmlHttpRequest_responseText(xmlHttpRequest *xhr)
{
    return g_strdup(xhr->responseText->str);
}

unsigned short
get_socket_handle(xmlHttpRequest *xhr, gchar *hostname,
                  gchar *port, gint *fd)
{
    int result;
    gint sock_fd = 0;
    struct addrinfo hints, *server, *ptr;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0;
    hints.ai_protocol = 0;

    result = getaddrinfo(hostname, port, &hints, &server);
    if (result) {
        g_warning(gai_strerror(result));
        return NETWORK_ERR;
    }

    for (ptr = server; ptr != NULL; ptr = ptr->ai_next) {
        sock_fd =
            socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        if (sock_fd == -1)
            continue;
        if (connect(sock_fd, ptr->ai_addr, ptr->ai_addrlen) == 0)
            break;
    }
    if (ptr == NULL) {
        g_warning("Can't connect.");
        return NETWORK_ERR;
    }
    *fd = sock_fd;
    return SUCCESS;
}

gchar *request_header_new(xmlHttpRequest *xhr, const gchar *hostname,
                          const gchar *path)
{
    GString *request_header = NULL;
    GList *headers = g_hash_table_get_keys(xhr->requestHeaders);
    int i;

    request_header = g_string_new("GET ");
    g_string_append(request_header, path);
    g_string_append(request_header, " HTTP/1.1\r\n");

    g_string_append(request_header, "Host: ");
    g_string_append(request_header, hostname);
    g_string_append(request_header, "\r\n");
    g_string_append(request_header, "Connection: close\r\n");

    for (i = 0; i < g_list_length(headers); i++) {
        gchar *key = g_list_nth_data(headers, i);
        gchar *value = g_hash_table_lookup(xhr->requestHeaders, key);
        g_string_append(request_header, key);
        g_string_append(request_header, ": ");
        g_string_append(request_header, value);
        g_string_append(request_header, "\r\n");
    }

    g_string_append(request_header, "\r\n");
    return g_string_free(request_header, FALSE);
}

void send_implement(xmlHttpRequest *xhr)
{
    gchar **url_split = NULL, **hostname_split = NULL, **ptr = NULL;
    gchar *hostname = NULL, *port = NULL, *path = NULL, *request_header =
        NULL;
    gchar buf[BUFSIZE + 1];
    gint fd, file_size = 0;

    GString *message = NULL;
    unsigned short result;
    jsval rval, status;
    JSString *value;

    g_debug("send_implement start");
    url_split = g_strsplit(xhr->url, "/", 4);
    ptr = url_split;

    hostname_split = g_strsplit(url_split[2], ":", 2);
    hostname = hostname_split[0];
    if (hostname_split[1])
        port = hostname_split[1];
    else
        port = "80";

    result = get_socket_handle(xhr, hostname, port, &fd);
    if (result != SUCCESS) {
        jsval result_v = INT_TO_JSVAL(result);
        xhr->result = result;
        g_strfreev(url_split);
        g_strfreev(hostname_split);
        JS_SetPendingException(xhr->cx, result_v);
	return;
    }

    path = g_strconcat("/", url_split[3], NULL);
    request_header = request_header_new(xhr, hostname, path);
    g_free(path);
    write(fd, request_header, strlen(request_header));
    g_free(request_header);

    message = g_string_new(NULL);
    while (TRUE) {
        ssize_t r;
        memset(&buf, 0, BUFSIZE + 1);
        r = read(fd, buf, BUFSIZE);
        file_size += r;
        g_string_append_len(message, buf, r);
        if (r == 0)
            break;
    }
    close(fd);
    parseMessage(xhr, message);
    g_string_free(message, TRUE);

    status = INT_TO_JSVAL(xhr->status);
    JS_SetProperty(xhr->cx, xhr->obj, "status", &status);
    g_strfreev(url_split);
    g_strfreev(hostname_split);
    g_debug("send_implement finish");

    value = JS_NewStringCopyZ(xhr->cx, xhr->responseText->str);
    rval = STRING_TO_JSVAL(value);
    JS_SetProperty(xhr->cx, xhr->obj, "responseText", &rval);

    xmlHttpRequest_changeReadyState(xhr, DONE);
}

unsigned short
xmlHttpRequest_send(xmlHttpRequest *xhr, const gchar *query)
{
    GError *err;
    g_return_val_if_fail(xhr, FALSE);
    g_return_val_if_fail(xhr->method, FALSE);
    g_return_val_if_fail(xhr->url, FALSE);

    xhr->query = g_strdup(query);
    if (xhr->async) {
        g_debug("Asynchronous Transfer start.");
        xhr->thread = g_thread_create((GThreadFunc)send_implement, xhr, TRUE, &err);
        g_return_val_if_fail(xhr->thread, FALSE);
    } else {
        g_debug("Synchronous Transfer start.");
        xhr->thread = g_thread_create((GThreadFunc)send_implement, xhr, TRUE, &err);
        g_return_val_if_fail(xhr->thread, FALSE);
        g_thread_join(xhr->thread);
    }
    g_debug("xmlHttpRequest_send result %d", xhr->result);
    return xhr->result;
}

gchar *readline(GString *message, int *i)
{
    GString *line = g_string_new(NULL);
    for (; *i < message->len; *i = *i + 1) {
        if (message->str[*i] == '\r') {
            gchar *retval = g_string_free(line, FALSE);
            *i = *i + 2;
            return retval;
        } else {
            g_string_append_c(line, message->str[*i]);
        }
    }
    return NULL;
}

unsigned short parseMessage(xmlHttpRequest *xhr, GString *message)
{
    gchar *line = NULL;
    gchar **status_split = NULL;
    int index = 0;
    GError *cache_err = NULL;
    gsize cache_size;
    GIOChannel *cache = NULL;
    GString *cache_file_full_path = g_string_new(g_get_home_dir());
    gchar *transfer_encoding;

    g_string_append(cache_file_full_path, "/");
    g_string_append(cache_file_full_path, DESKTOP_GADGET_PLAYER_SUBDIR);
    g_string_append(cache_file_full_path, CACHE_SUBDIR);
    if (g_mkdir_with_parents(cache_file_full_path->str, 0700)) {
        g_warning("failed to create cache dir %s",
                  cache_file_full_path->str);
        return ABORT_ERR;
    }

    g_string_append_printf(cache_file_full_path, "%u", g_str_hash(xhr->url));
    g_debug(cache_file_full_path->str);
    cache = g_io_channel_new_file(cache_file_full_path->str, "w", &cache_err);
    if (cache_err) {
        g_error_free(cache_err);
        return ABORT_ERR;
    }

    line = readline(message, &index);
    status_split = g_strsplit(line, " ", 3);
    g_free(line);

    xhr->status = atoi(status_split[1]);
    g_strfreev(status_split);

    while (TRUE) {
        line = readline(message, &index);
        if (line == NULL || strlen(line) == 0) {
            break;
        }
        parseHeaderLine(xhr, line);
        g_free(line);
    }

    xmlHttpRequest_changeReadyState(xhr, HEADERS_RECEIVED);
    xmlHttpRequest_changeReadyState(xhr, LOADING);

    transfer_encoding = xmlHttpRequest_getResponseHeader(xhr, "Transfer-Encoding");
    if(transfer_encoding == NULL || strcmp(transfer_encoding, "chunked")){
	while (TRUE) {
	    if (index >= message->len)
		break;
	    g_string_append_c(xhr->responseText, message->str[index++]);
	}
    }else{
	int ch;
	guint64 len;
	GString *line = g_string_new(NULL);
	while (TRUE) {
	    if (index >= message->len){
		break;
	    }
	    ch = message->str[index++];
	    g_string_append_c(line, ch);
	    if(ch == '\n'){
		len = g_ascii_strtoull(line->str, NULL, 16);
		while(len-- > 0){
		    g_string_append_c(xhr->responseText, message->str[index++]);
		}
		g_string_free(line, TRUE);
		line = g_string_new(NULL);
	    }
	}
	g_string_free(line, TRUE);
    }

    g_io_channel_set_encoding(cache, NULL, &cache_err);
    g_io_channel_write_chars(cache, xhr->responseText->str,
                             xhr->responseText->len, &cache_size,
                             &cache_err);
    g_io_channel_close(cache);
    if (cache_err) {
        g_error_free(cache_err);
        return ABORT_ERR;
    }
    return SUCCESS;
}

unsigned short parseHeaderLine(xmlHttpRequest *xhr, const gchar *line)
{
    gchar **split = g_strsplit(line, ": ", 2);
    xmlHttpRequest_setResponseHeader(xhr, g_strdup(split[0]), g_strdup(split[1]));
    g_strfreev(split);
    return SUCCESS;
}

gchar *request_headers_new(GHashTable *headers)
{
    GString *header = g_string_new(NULL);
    GList *keys = g_hash_table_get_keys(headers);
    gchar *key = NULL, *value = NULL;
    int i;
    for (i = 0; i < g_list_length(keys); i++) {
        key = g_list_nth_data(keys, i);
        value = g_hash_table_lookup(headers, key);
        g_string_append(header, key);
        g_string_append(header, ": ");
        g_string_append(header, value);
        g_string_append(header, "\r\n");
    }
    return g_string_free(header, FALSE);
}
